﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Web;
using System.Xml;

namespace SCSInternetBanking.LibertyReserve
{
    public sealed class XmlApiAgent : AbstractApiAgent
    {
        public XmlApiAgent(ApiCredentials credentials)
            : base(credentials)
        {
        }

        #region IApiAgent Members

        public override Balance Balance()
        {
            StringBuilder request = new StringBuilder();
            request.AppendFormat("<BalanceRequest id='{0}'>", DateTime.UtcNow.Ticks);
            request.Append(AuthToken());
            request.Append("<Balance><CurrencyId>usd</CurrencyId></Balance>");
            request.Append("<Balance><CurrencyId>euro</CurrencyId></Balance>");
            request.Append("<Balance><CurrencyId>gold</CurrencyId></Balance>");
            request.Append("</BalanceRequest>");

            XmlDocument response = SendXmlRequest("/xml/balance", request.ToString());
            return ParseBalanceResponse(response);
        }

        public override string AccountName(string accountNumber)
        {
            StringBuilder request = new StringBuilder();
            request.AppendFormat("<AccountNameRequest id='{0}'>", DateTime.UtcNow.Ticks);
            request.Append(AuthToken());
            request.AppendFormat("<AccountName><AccountToRetrieve>{0}</AccountToRetrieve></AccountName>", accountNumber);
            request.Append("</AccountNameRequest>");

            XmlDocument response = SendXmlRequest("/xml/accountname", request.ToString());
            return ParseAccountNameResponse(response);
        }

        public override HistoryResult History(HistorySpecification specification, int pageIndex, int pageSize)
        {
            StringBuilder request = new StringBuilder();
            request.AppendFormat("<HistoryRequest id='{0}'>", DateTime.UtcNow.Ticks);
            request.Append(AuthToken());
            request.Append("<History>");
            request.AppendFormat("<From>{0:yyyy-MM-dd HH:mm:ss}</From><Till>{1:yyyy-MM-dd HH:mm:ss}</Till>", specification.From, specification.Till);
            request.AppendFormat("<CurrencyId>{0}</CurrencyId>", specification.Currency.ToString().ToLower());
            request.AppendFormat("<Direction>{0}</Direction>", specification.Direction.ToString().ToLower());
            request.AppendFormat("<Source>{0}</Source>", specification.Source.ToString().ToLower());
            request.AppendFormat("<Anonymous>{0}</Anonymous>", specification.Private.ToString().ToLower());
            request.AppendFormat("<TransferId>{0}</TransferId>", specification.Reference);
            request.AppendFormat("<CorrespondingAccountId>{0}</CorrespondingAccountId>", specification.RelatedAccount);
            request.AppendFormat("<AmountFrom>{0}</AmountFrom><AmountTo>{1}</AmountTo>", specification.AmountFrom, specification.AmountTo);
            request.AppendFormat("<Pager><PageNumber>{0}</PageNumber><PageSize>{1}</PageSize></Pager>", pageIndex, pageSize);
            request.Append("</History></HistoryRequest>");

            XmlDocument response = SendXmlRequest("/xml/history", request.ToString());
            return new HistoryResult()
            {
                Items = ParseHistoryResponse(response),
                HasMore = HasMore(response)
            };
        }

        public override HistoryItem FindTransaction(long batchNumber)
        {
            StringBuilder request = new StringBuilder();
            request.AppendFormat("<FindTransactionRequest id='{0}'>", DateTime.UtcNow.Ticks);
            request.Append(AuthToken());
            request.AppendFormat("<FindTransaction><ReceiptId>{0}</ReceiptId></FindTransaction>", batchNumber);
            request.Append("</FindTransactionRequest >");

            XmlDocument response = SendXmlRequest("/xml/findtransaction", request.ToString());
            HistoryItem[] resultList = ParseHistoryResponse(response);
            return resultList.Length > 0 ? resultList[0] : null;
        }

        public override HistoryItem Transfer(Transfer transfer)
        {
            StringBuilder request = new StringBuilder();
            request.AppendFormat("<TransferRequest id='{0}'>", DateTime.UtcNow.Ticks);
            request.Append(AuthToken());
            request.Append("<Transfer><TransferType>transfer</TransferType>");

            request.AppendFormat("<TransferId>{0}</TransferId>", transfer.Reference);
            request.AppendFormat("<Payee>{0}</Payee>", transfer.Payee);
            request.AppendFormat("<CurrencyId>{0}</CurrencyId>", transfer.Currency.ToString().ToLower());
            request.AppendFormat("<Amount>{0}</Amount>", transfer.Amount);
            request.AppendFormat("<Memo>{0}</Memo>", transfer.Memo);
            request.AppendFormat("<Anonymous>{0}</Anonymous>", transfer.Private.ToString().ToLower());
            request.AppendFormat("<PaymentPurpose>{0}</PaymentPurpose>", transfer.Purpose.ToString().ToLower());

            request.Append("</Transfer></TransferRequest>");

            XmlDocument response = SendXmlRequest("/xml/transfer", request.ToString());

            HistoryItem[] resultList = ParseHistoryResponse(response);
            return resultList.Length > 0 ? resultList[0] : null;
        }

        #endregion

        private string AuthToken()
        {
            return string.Format(CultureInfo.InvariantCulture, "<Auth><AccountId>{0}</AccountId><ApiName>{1}</ApiName><Token>{2}</Token></Auth>",
                Credentials.AccountNumber,
                Credentials.ApiName,
                Credentials.CreateSecurityToken());
        }

        private static Balance ParseBalanceResponse(XmlDocument response)
        {
            CheckError(response);
            XmlNodeList balanceList = response.SelectNodes("BalanceResponse/Balance/Value");
            if (balanceList == null || balanceList.Count != 3)
            {
                throw new ApiException("Invalid response format");
            }

            return new Balance()
            {
                Usd = Decimal.Parse(balanceList[0].InnerText, CultureInfo.InvariantCulture),
                Euro = Decimal.Parse(balanceList[1].InnerText, CultureInfo.InvariantCulture),
                Gold = Decimal.Parse(balanceList[2].InnerText, CultureInfo.InvariantCulture)
            };
        }

        private static string ParseAccountNameResponse(XmlDocument response)
        {
            CheckError(response);
            XmlNode name = response.SelectSingleNode("AccountNameResponse/AccountName/Name");
            if (name == null)
            {
                throw new ApiException("Invalid response format");
            }

            return name.InnerText;
        }

        private static bool HasMore(XmlDocument response)
        {
            XmlNode node = response.SelectSingleNode("HistoryResponse/Pager/HasMore");
            if (node == null)
            {
                throw new ApiException("Invalid response format");
            }

            return "true" == node.InnerText;
        }

        private static HistoryItem[] ParseHistoryResponse(XmlDocument response)
        {
            List<HistoryItem> list = new List<HistoryItem>();
            CheckError(response);
            XmlNodeList transfers = response.SelectNodes("//Receipt");
            foreach (XmlNode node in transfers)
            {
                list.Add(ParseHistoryItem(node));
            }

            return list.ToArray();
        }

        private static HistoryItem ParseHistoryItem(XmlNode node)
        {
            return new HistoryItem()
            {
                Batch = long.Parse(node.SelectSingleNode("ReceiptId").InnerText, CultureInfo.InvariantCulture),
                Date = DateTime.ParseExact(node.SelectSingleNode("Date").InnerText, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture),
                Payer = node.SelectSingleNode("Payer").InnerText,
                Payee = node.SelectSingleNode("Payee").InnerText,
                PayerName = node.SelectSingleNode("PayerName").InnerText,
                PayeeName = node.SelectSingleNode("PayeeName").InnerText,
                Source = (Source)Enum.Parse(typeof(Source), node.SelectSingleNode("Source").InnerText, true),
                Reference = node.SelectSingleNode("TransferId").InnerText,
                Memo = node.SelectSingleNode("Memo").InnerText,
                Amount = decimal.Parse(node.SelectSingleNode("Amount").InnerText, CultureInfo.InvariantCulture),
                Fee = decimal.Parse(node.SelectSingleNode("Fee").InnerText, CultureInfo.InvariantCulture),
                Balance = decimal.Parse(node.SelectSingleNode("ClosingBalance").InnerText, CultureInfo.InvariantCulture),
                Currency = (Currency)Enum.Parse(typeof(Currency), node.SelectSingleNode("CurrencyId").InnerText, true),
                Private = bool.Parse(node.SelectSingleNode("Anonymous").InnerText)
            };
        }

        private static void CheckError(XmlDocument doc)
        {
            XmlNode error = doc.SelectSingleNode("//Error");
            if (error != null)
            {
                string errorCode = error.SelectSingleNode("Code").InnerText;
                string errorText = error.SelectSingleNode("Text").InnerText;
                string errorDescription = error.SelectSingleNode("Description").InnerText;
                throw new ApiException(String.Format(CultureInfo.InvariantCulture, "{0}: {1}",
                    errorText, errorDescription))
                {
                    ErrorCode = Int32.Parse(errorCode, CultureInfo.InvariantCulture)
                };
            }
        }

        private XmlDocument SendXmlRequest(string path, string requestData)
        {
            string response = SendRequest(path, string.Concat("req=", HttpUtility.UrlEncode(requestData)));
            XmlDocument responseDocument = new XmlDocument();
            try
            {
                responseDocument.LoadXml(response);
            }
            catch (XmlException xex)
            {
                throw new ApiException("An error occured while parsing response XML", xex);
            }

            return responseDocument;
        }
    }
}
